Modbus TCP报文详解附C#demo

您所在的位置:网站首页 modbus tcp报文结构 Modbus TCP报文详解附C#demo

Modbus TCP报文详解附C#demo

2023-06-04 13:31| 来源: 网络整理| 查看: 265

重要:本文最后更新于2023-05-25 19:47:54,某些文章具有时效性,若有错误或已失效,请在下方留言或联系代码狗

Modbus TCP是一种基于TCP/IP的工业通信协议,广泛应用于自动化设备、工业控制系统等领域。相较于基于串行通信的Modbus RTU和Modbus ASCII协议,Modbus TCP具有更高的通信速率和更强的抗干扰能力,因此在现代工业通信系统中具有广泛的应用前景。

一、Modbus TCP报文结构

一个完整的Modbus TCP报文包含以下几个部分:

事务标识符(Transaction Identifier,2字节):用于标识请求报文和响应报文之间的关联,通常由客户端生成,服务器端在响应报文中原样返回。 协议标识符(Protocol Identifier,2字节):用于区分不同的协议,对于Modbus TCP协议,该字段的值固定为0x0000。 长度字段(Length,2字节):表示后续报文中的字节数,包括单元标识符、功能码和数据字段。 单元标识符(Unit Identifier,1字节):用于标识Modbus设备的地址,取值范围为1-247。在Modbus TCP协议中,该字段的作用较小,通常设为0xFF。 功能码(Function Code,1字节):用于指示报文的操作类型,例如读取寄存器、写入寄存器等。功能码的取值范围为1-255,但实际应用中常用的功能码有限。 数据字段(Data,不定长度):根据功能码的不同,该字段用于携带不同的数据信息,例如寄存器地址、寄存器值等。 二、功能码与数据编码

常用的Modbus TCP功能码及其对应的操作如下:

读取线圈状态(Read Coils,功能码0x01):读取1个或多个线圈的状态(开/关)。 读取离散输入状态(Read Discrete Inputs,功能码0x02):读取1个或多个离散输入的状态(开/关)。 读取保持寄存器(Read Holding Registers,功能码0x03):读取1个或多个保持寄存器的值。 读取输入寄存器(Read Input Registers,功能码0x04):读取1个或多个输入寄存器的值。 写单个线圈(Write Single Coil,功能码0x05):设置1个线圈的状态(开/关)。 写单个保持寄存器(Write Single Register,功能码0x06):设置1个保持寄存器的值。 写多个线圈(Write Multiple Coils,功能码0x0F):设置多个线圈的状态(开/关)。 写多个保持寄存器(Write Multiple Registers,功能码0x10):设置多个保持寄存器的值。

数据编码主要涉及寄存器地址、寄存器值、线圈状态等信息。在Modbus TCP协议中,寄存器地址和寄存器值均采用2字节(16位)的无符号整数表示,线圈状态则用1字节表示,其中0xFF表示开,0x00表示关。

三、报文示例

假设我们需要读取一个Modbus设备的保持寄存器,寄存器地址为0x1000,读取长度为2个寄存器。根据上述介绍,我们可以生成如下请求报文:

事务标识符:0x0001(假设为1)

协议标识符:0x0000

长度字段:0x0006(后续报文共6字节)

单元标识符:0xFF

功能码:0x03(读取保持寄存器)

数据字段:0x1000 0x0002(寄存器地址为0x1000,读取长度为2)

将上述字段组合在一起,得到完整的请求报文:0x0001 0x0000 0x0006 0xFF 0x03 0x1000 0x0002。

设备收到请求报文后,将返回如下响应报文:

事务标识符:0x0001(与请求报文相同)

协议标识符:0x0000

长度字段:0x0007(后续报文共7字节)

单元标识符:0xFF

功能码:0x03(与请求报文相同)

数据长度:0x04(返回数据共4字节)

数据字段:0x1234 0x5678(假设保持寄存器的值分别为0x1234和0x5678)

将上述字段组合在一起,得到完整的响应报文:0x0001 0x0000 0x0007 0xFF 0x03 0x04 0x1234 0x5678。

四、报文解析

在收到响应报文后,我们需要对其进行解析以获取所需的寄存器数据。解析步骤如下:

验证事务标识符:将响应报文中的事务标识符(0x0001)与请求报文中的事务标识符进行比较,确保它们相同。 验证协议标识符:检查响应报文中的协议标识符(0x0000)是否为Modbus TCP协议的固定值。 验证长度字段:检查响应报文中的长度字段(0x0007)是否与实际报文长度相符。 验证单元标识符:将响应报文中的单元标识符(0xFF)与请求报文中的单元标识符进行比较,确保它们相同。 验证功能码:将响应报文中的功能码(0x03)与请求报文中的功能码进行比较,确保它们相同。 解析数据长度:从响应报文中读取数据长度字段(0x04),确认返回数据的字节数。 提取数据字段:根据数据长度字段,提取响应报文中的数据字段(0x1234 0x5678),并按照寄存器地址顺序解析为对应的寄存器值。

通过上述解析步骤,我们成功获取了目标设备的保持寄存器数据,分别为0x1234和0x5678。

五、C#示例代码

使用C#封装一个类,用于构造和解析报文。

using System; using System.Net.Sockets; using System.Text; namespace ModbusTcpClient { class ModbusTcp { private TcpClient _tcpClient; private NetworkStream _networkStream; public ModbusTcp(string ipAddress, int port) { _tcpClient = new TcpClient(ipAddress, port); _networkStream = _tcpClient.GetStream(); } public void CloseConnection() { _networkStream.Close(); _tcpClient.Close(); } public byte[] CreateRequest(ushort transactionId, byte unitId, byte functionCode, ushort registerAddress, ushort registerCount) { byte[] request = new byte[12]; // Transaction Identifier byte[] transactionIdBytes = BitConverter.GetBytes(transactionId); request[0] = transactionIdBytes[1]; request[1] = transactionIdBytes[0]; // Protocol Identifier request[2] = 0x00; request[3] = 0x00; // Length request[4] = 0x00; request[5] = 0x06; // Unit Identifier request[6] = unitId; // Function Code request[7] = functionCode; // Register Address byte[] registerAddressBytes = BitConverter.GetBytes(registerAddress); request[8] = registerAddressBytes[1]; request[9] = registerAddressBytes[0]; // Register Count byte[] registerCountBytes = BitConverter.GetBytes(registerCount); request[10] = registerCountBytes[1]; request[11] = registerCountBytes[0]; return request; } public void SendRequest(byte[] request) { _networkStream.Write(request, 0, request.Length); } public byte[] ReceiveResponse() { byte[] response = new byte[256]; int bytesRead = _networkStream.Read(response, 0, response.Length); Array.Resize(ref response, bytesRead); return response; } public ushort[] ParseResponse(byte[] response) { int dataLength = response[8]; ushort[] registerValues = new ushort[dataLength / 2]; for (int i = 0; i < dataLength / 2; i++) { registerValues[i] = BitConverter.ToUInt16(new byte[] { response[9 + i * 2], response[10 + i * 2] }, 0); } return registerValues; } } }

使用这个类,你可以像下面这样创建一个Modbus TCP客户端,发送请求报文并解析响应报文:

using System; namespace ModbusTcpClient { class Program { static void Main(string[] args) { ModbusTcp modbusTcpClient = new ModbusTcp("192.168.1.1", 502); byte[] request = modbusTcpClient.CreateRequest(0x0001, 0xFF, 0x03, 0x0000, 0x0002); modbusTcpClient.SendRequest(request); byte[] response = modbusTcpClient.ReceiveResponse(); ushort[] registerValues = modbusTcpClient.ParseResponse(response); Console.WriteLine("Register values:"); foreach (ushort value in registerValues) { Console.WriteLine(value); } modbusTcpClient.CloseConnection(); } } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3